package engine;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;

import menu.IMenu;
import menu.MainMenu;
import menu.PauseMenu;

import view.SpaceCamera;

import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.IRenderer;
import com.threed.jpct.Loader;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.procs.BloomGLProcessor;
import com.threed.jpct.util.SkyBox;

import font.GLFont;

public class Application {
	private World world;
	private World mainMenuWorld;
	private World pauseMenuWorld;
	private FrameBuffer buffer;
	private SpaceCamera spaceCamera;

	private Universe universe;
	private SkyBox skyBox;
	private BloomGLProcessor bloom;
	private MainMenu mainMenu;
	private PauseMenu pauseMenu;

	private FPSCounter fpsCounter;
	private boolean showFPS;

	private static GLFont font;
	private static GLFont bigFont;

	Timer logicTimer;

	private final int LOGIC_FPS = 60;

	private boolean f1ButtonDown, escButtonDown;
	private boolean pauseOn = false;

	private enum GameState {
		MAIN_MENU, START_GAME, PLAY_GAME, PAUSE_GAME, STOP_GAME, END_GAME
	}

	private GameState actualGameState;
	private String defeatMessage;
	private String victoryMessage;
	private Dimension defeatDim;
	private Dimension victoryDim;

	public Application() {
		Resources.loadResources();
		font = new GLFont(new Font("Arial", Font.BOLD, 30));
		bigFont = new GLFont(new Font("Arial", Font.BOLD, 60));

		actualGameState = GameState.MAIN_MENU;
		
		TextureManager.getInstance().addTexture("orange", new Texture(32, 32, Color.ORANGE));
		TextureManager.getInstance().addTexture("black", new Texture(32, 32, Color.BLACK));
		TextureManager.getInstance().addTexture("red", new Texture(8, 8, Color.RED));
	}

	private void startGame() {
		buffer.clear(java.awt.Color.BLACK);
		font.blitString(buffer, "LOADING...", 600, 70, 100, Color.white);
		buffer.update();
		buffer.displayGLOnly();
		onInit();
		onLoad();
		actualGameState = GameState.PLAY_GAME;
	}

	private void playGame() {
		if (Keyboard.KEY_ESC && universe.getVictory() != 0) {
			actualGameState = GameState.MAIN_MENU;
			Keyboard.KEY_ESC = false;
		} else if (Keyboard.KEY_ESC && !escButtonDown) {
			escButtonDown = true;
			pauseOn = !pauseOn;
		} else if (!Keyboard.KEY_ESC) {
			escButtonDown = false;
		}

		if (pauseOn) {
			actualGameState = GameState.PAUSE_GAME;
			logicTimer.stop();
			universe.stopPointIncrementation();
		} else {
			long ticks = logicTimer.getElapsedTicks();
			for (int i = 0; i < ticks; i++) {
				onLogicLoop();
			}
			onLoop();
			fpsCounter.addFrame();
		}
	}

	private void pauseGame() {
		int result = pauseMenu.getResult();
		if (result == 1) {
			actualGameState = GameState.PLAY_GAME;
			pauseOn = false;
			logicTimer.start();
			universe.startPointIncrementation();
		} else if (result == 2) {
			actualGameState = GameState.START_GAME;
			pauseOn = false;
		} else if (result == 3) {
			actualGameState = GameState.MAIN_MENU;
			pauseOn = false;
		}
		onPauseMenuLoop();
	}

	private void stopGame() {

	}

	private boolean useMenu() {
		int result = mainMenu.getResult();
		if (result == 1) {
			actualGameState = GameState.START_GAME;
		} else if (result == 2) {
			mainMenu.dispose();
			return false;
		}
		onMainMenuLoop();
		return true;
	}

	/**
	 * Implements game lifecycle.
	 */
	public void start() {
		onWindowInit();
		onMainMenuInit();
		onPauseMenuInit();
		
		while (!org.lwjgl.opengl.Display.isCloseRequested()) {
			if (actualGameState == GameState.START_GAME) {
				startGame();
			} else if (actualGameState == GameState.PLAY_GAME) {
				playGame();
			} else if (actualGameState == GameState.PAUSE_GAME) {
				pauseGame();
			} else if (actualGameState == GameState.STOP_GAME) {
				stopGame();
			} else if (actualGameState == GameState.MAIN_MENU) {
				if (!useMenu()) {
					break;
				}
			}
		}
		onExit();
	}

	private void onWindowInit() {
		Config.maxPolysVisible = 100000;
		TextureManager.getInstance().addTexture("yellow", new Texture(32, 32, Color.YELLOW));

		buffer = new FrameBuffer(800, 600, FrameBuffer.SAMPLINGMODE_GL_AA_4X);
		buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
		buffer.enableRenderer(IRenderer.RENDERER_OPENGL);

		defeatMessage = "DEFEAT";
		defeatDim = font.getStringBounds(defeatMessage);

		victoryMessage = "VICTORY";
		victoryDim = font.getStringBounds(victoryMessage);
	}

	private void onMainMenuInit() {
		mainMenuWorld = new World();
		mainMenuWorld.setAmbientLight(255, 255, 255);

		mainMenu = new MainMenu();
		mainMenu.initialize(mainMenuWorld, buffer);
	}

	private void onPauseMenuInit() {
		pauseMenuWorld = new World();
		pauseMenuWorld.setAmbientLight(255, 255, 255);

		pauseMenu = new PauseMenu();
		pauseMenu.initialize(pauseMenuWorld, buffer);
	}

	private void onMenuLoop(IMenu menu, World menuWorld) {
		buffer.clear(java.awt.Color.BLACK);

		menu.update(menuWorld, buffer);

		menuWorld.renderScene(buffer);
		menuWorld.draw(buffer);

		buffer.update();
		buffer.displayGLOnly();
	}

	private void onPauseMenuLoop() {
		onMenuLoop(pauseMenu, pauseMenuWorld);
	}

	private void onMainMenuLoop() {
		onMenuLoop(mainMenu, mainMenuWorld);
	}

	/**
	 * Initializes buffer and world basic preferences.
	 */
	private void onInit() {
		world = new World();
		world.setAmbientLight(255, 255, 255);

		spaceCamera = new SpaceCamera(world.getCamera());

		fpsCounter = new FPSCounter();
		showFPS = false;

		logicTimer = new Timer(1000 / LOGIC_FPS);
		logicTimer.start();

		bloom = new BloomGLProcessor();
		bloom.init(buffer);

		f1ButtonDown = false;
	}

	/**
	 * Loads all needed content e.g. textures, models and creates world.
	 */
	private void onLoad() {
		universe = UniverseFactory.createRandom(15);
		if (universe == null) {
			System.exit(1);
		}
		universe.onInit(world, buffer);

		createSun();

		skyBox = new SkyBox(400.0f);
		skyBox.compile();
	}

	/**
	 * That method is executed 60 times per second with warranty. Speed can be
	 * modified by changing LOGIC_FPS field;
	 */
	private void onLogicLoop() {
		universe.onLogicUpdate(world, buffer);
	}

	/**
	 * That method is executed every single frame.
	 */
	private void onLoop() {
		Keyboard.handleKeys();

		if (Keyboard.KEY_F1 && !f1ButtonDown) {
			f1ButtonDown = true;
			showFPS = !showFPS;
		} else if (!Keyboard.KEY_F1) {
			f1ButtonDown = false;
		}

		spaceCamera.update();

		buffer.clear(java.awt.Color.BLACK);

		skyBox.render(world, buffer);

		world.renderScene(buffer);
		world.draw(buffer);

		// draw always just before buffer update.
		bloom.process();
		universe.onUpdate(world, buffer);

		if (universe.getVictory() == -1) {
			bigFont.blitString(buffer, defeatMessage, 270, 30 + defeatDim.height, 100, Color.white);
		} else if (universe.getVictory() == 1) {
			bigFont.blitString(buffer, victoryMessage, 270, 30 + victoryDim.height, 100, Color.white);
		}

		if (showFPS)
			fpsCounter.draw(buffer, 10, 30);

		buffer.update();
		buffer.displayGLOnly();
	}

	/**
	 * Frees resources, disables renderer.
	 */
	private void onExit() {
		if (skyBox != null) {
			skyBox.dispose();
		}
		buffer.disableRenderer(IRenderer.RENDERER_OPENGL);
		buffer.dispose();
		System.exit(0);
	}

	public void createSun() {
		Object3D sun = Loader.loadOBJ("content/planet.obj", null, 1.0f)[0];
		if (sun == null) {
			Logger.log("Could not load sun object.");
		}
		sun.setScale(10.0f);
		sun.setTexture("SUN");
		sun.compile();
		sun.build();
		world.addObject(sun);
	}
}
